#ifndef _GRAPHICS_H
#define _GRAPHICS_H

#include <tchar.h>
#include <cmath>

#include "math/color.h"
#include "math/Edge.h"
#include "math/mat4x4.h"
#include "math/Frustum.h"

namespace TG
{
	class Shader;
	class Texture;

	enum    DATETYPE
	{
		DT_BYTE,
		DT_FLOAT,
	};

	enum CULLFACE
	{
		NONE,
		BACK,
		FRONT,
		BOTH,
	};

	struct RenderState
	{
		int width;
		int height;
		mat4x4 matModel;
		mat4x4 matView;
		mat4x4 matProj;
		mat4x4 matViewProj;

		vec4 viewport;
		vec3 cameraDir;

		Texture* texture;
	};

	struct Buffer
	{
		int         size;
		DATETYPE    type;
		int         stride;
		const void* data;
	};

	class Graphics
	{
	public:
		int width;
		int height;

	private:
		Rgba* _buffer;
		float* _depth;

		Buffer _vertexBuffer;
		Buffer _colorBuffer;
		Buffer _normalBuffer;
		Buffer _indexBuffer;
		Buffer _uvBuffer;

		CULLFACE _cullMode;

		Shader* _shader;
		RenderState _state;
	public:
		Graphics(int width, int height, void* buffer) : width(width), height(height)
		{
			_buffer = (Rgba*)buffer;
			_depth = new float[width * height];
			memset(_depth, 0, width * height);

			_state.width = width;
			_state.height = height;
		}

		~Graphics()
		{
			delete[] _depth;
		}

		void drawPoint(int x, int y, Rgba color, float size);

		void drawLine(float x0, float y0, float x1, float y1, Rgba c0, Rgba c1);

		void drawTriangle(const Vertex &p0, const Vertex &p1, const Vertex &p2);

		void clear();

		void vertexPointer(int size, DATETYPE type, int stride, const void* data);
		void colorPointer(int size, DATETYPE type, int stride, const void* data);
		void normalPointer(int size, DATETYPE type, int stride, const void* data);
		void indexPointer(int size, DATETYPE type, int stride, const void* data);
		void uvPointer(int size, DATETYPE type, int stride, const void* data);

		void drawArray(int start, int count);
		void drawElement(int start, int count);

		void setViewPort(float x, float y, float w, float h);
		void setViewPort(const vec4 &port);
		void setModelMatrix(const mat4x4 &mat);
		void setViewMatrix(const mat4x4 &mat);
		void setProjMatrix(const mat4x4 &mat);
		void setCameraDir(const vec3 &dir);
		void setCullFace(CULLFACE face);
		void bindTexture(Texture* texture);
		void bindShader(Shader* shader);
	private:
		inline void setPixel(unsigned int x, unsigned int y, Rgba color)
		{
			if (x > width || y > height) return;

			_buffer[y * width + x] = color;
		}

		void drawEdge(const Edge& e0, const Edge& e1);

		void drawSpan(const Vertex &v0, const Vertex &v1);

		int dtSize(DATETYPE type)
		{
			if (type == DT_BYTE)
			{
				return sizeof(char);
			}
			else if (type == DT_FLOAT)
			{
				return sizeof(float);
			}
			return 0;
		}

		vec3 piplineTransform(vec3 &pos);

		bool cullFace(const vec3 &p0, const vec3 &p1, const vec3 &p2);

		Vertex lerpY(const Vertex &v0, const Vertex &v1, int &y, float &scale, float &offsetX);
		Vertex lerpX(const Vertex &v0, const Vertex &v1, int x, float scale);

		bool depthTest(int x, int y, float z);

		Rgba blend(Rgba src, Rgba dst)
		{
			return colorLerp(src, dst, dst.a / 255.0f);
		}
	};
}
#endif